/*
PearReview - The peer review assistant.
Copyright (C) 2009  Dimitrij Pankratz, Anton Musichin
http://www.pearreview.com, info@pearreview.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package com.pearreview.reviewerplugin.io;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Formatter;
import java.util.LinkedList;
import java.util.List;

import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Chunk;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.PdfDestination;
import com.itextpdf.text.pdf.PdfOutline;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import com.pearreview.application.Messages;
import com.pearreview.reviewerplugin.Report;
import com.pearreview.util.pdf.PChunk;
import com.pearreview.util.pdf.PParagraph;
import com.pearreview.util.pdf.PdfFindingsWriter;
import com.pearreview.util.pdf.PdfImage;
import com.pearreview.util.pdf.PdfImageWriter;
import com.pearreview.util.pdf.PdfPageEvent;
import com.pearreview.util.review.Finding;

public class PDFReportWriter implements ReportWriter, Serializable {
	
	private static final long serialVersionUID = -969663910020356594L;
	public static final String PDF_EXTENSION = ".pdf";
	public static final String FILE_PATTERN = "file:/.+";
	
	private boolean printImages = false;
	private int imageNumber = 0;
	private ArrayList<PdfImage> fImages = new ArrayList<PdfImage>();
	
	public PDFReportWriter() {
	}
	
	/**
	 * 
	 * @param printImages
	 */
	public void setPrintImages(boolean printImages) {
		this.printImages = printImages;
	}
	
	@Override
	public void write(Report report, File file){
		
	}
	
	@Override
	public void write(Report report, OutputStream stream) throws 
			DocumentException, IOException{

		fImages = new ArrayList<PdfImage>();
		
		//padding left, right, top, bottom = 50, 50, 50, 50
		Document doc = new Document(PageSize.A4, 50, 50, 60, 50);
		
		PdfWriter writer = PdfWriter.getInstance(doc, stream);
		//writer.setViewerPreferences(PdfWriter.FitWindow);
		PdfDestination dest = new PdfDestination(PdfDestination.FIT);
		writer.setViewerPreferences(PdfWriter.CenterWindow); 
		
		writer.setPageEvent(new PdfPageEvent());
		
		
		//String reviewName = (isEmpty(report.getName()))?
		//		getEmptyField():
		//		report.getName();
				
		//this.writeHeader(doc,
		//	Messages.getString("PearReview.protocol.protocol") 
		//	+ " - " + reviewName);
		//this.writerFooter(doc, Messages.getString(
		//	"PearReview.invitation.page") + " ");

		doc.open();
		
		new PdfOutline(writer.getDirectContent().getRootOutline(), dest, 
				Messages.getString("PearReview.protocol.protocol"));
			

		//add heading
		this.createDescription(doc, Messages.getString(
				"PearReview.Reviewer.report.report"));
		
		PTable table = new PTable(1);
		table.setKeepTogether(false);
		table.setSplitLate(false);
		table.setSplitRows(false);
		
		//add review info
		writeReviewInformation(report, table);
		
		//add results
		createResultText(report, table);
		
		PTable tableSide = new PTable(1);
		tableSide.setKeepTogether(false);
		tableSide.setSplitLate(false);
		tableSide.setSplitRows(false);
		
		//add date and time
		createDateTime(report, tableSide);
		
		//add product info
		this.createProductText(report, tableSide);
		
		
		PdfPTable wrapper = new PdfPTable(2);
		float[] widths = {2f, 1f};
		wrapper.setWidths(widths);
		wrapper.setWidthPercentage(100);
		
		PdfPCell cell2 = new PdfPCell(table);
		cell2.setHorizontalAlignment(PdfPCell.ALIGN_LEFT);
		cell2.setBorder(0);
		wrapper.addCell(cell2);
		PdfPCell sideCell = new PdfPCell(tableSide);
		sideCell.setBorder(0);
		sideCell.setPaddingLeft(30);
		wrapper.addCell(sideCell);
		
		wrapper.setKeepTogether(false);
		wrapper.setSplitLate(false);
		doc.add(wrapper);
		
		
		//add findings
		doc.newPage();
		if (report.getFindings() != null &&
				report.getFindings().size() > 0) {
			PdfFindingsWriter findinsWriter = 
				new PdfFindingsWriter(doc, writer);
			findinsWriter.writeFindings(report.getFindings(), fImages, 
					printImages, imageNumber);
		}
		
		
		//add images
		if (printImages) {
				doc.newPage();
				if (report.getFindings() != null &&
						report.getFindings().size() > 0) {
					new PdfImageWriter().writeImages(fImages, doc, writer);
				}
		}
		
		if (fImages.size() > 0)
			writer.setViewerPreferences(PdfWriter.PageModeUseAttachments);
		
		doc.close();
		
		writer.close();
	}

	public String getFileExtension() {
		return PDF_EXTENSION;
	}
	
	protected void attachFiles(PdfWriter writer, Finding finding) 
			throws IOException {
		/* API doc
		 * 
		public void PdfWriter::addFileAttachment(String description,
                byte[] fileStore,
                String file,
                String fileDisplay)
         throws IOException
         */
		
		for(URI ref: finding.getExternalReferences()) {
			File file = new File(ref);
			String description =Messages.getString(
					"PearReview.protocol.external_reference.description");
			
			Formatter formatter = new Formatter();
			description = 
					formatter.format(description, finding.getId()).toString();
			
			writer.addFileAttachment(description, 
					null, 
					file.toString(), 
					file.getName());
		}
	}
	
	private void createDescription(Document doc, String text) 
			throws DocumentException {
		
		Chunk cHeading = new PChunk(text);
		Font font = cHeading.getFont();
		font.setSize(32f);
		cHeading.setFont(font);

		Paragraph pHeading = new PParagraph();
		pHeading.setAlignment(Element.ALIGN_CENTER);
		pHeading.add(cHeading);
		//pHeading.setSpacingAfter(30f);
		
		PdfPTable table = new PdfPTable(1);
		table.setWidthPercentage(50);
		
		PdfPCell cell = new PdfPCell(pHeading);
		cell.setBorder(0);
		cell.setPaddingBottom(30);
		cell.setBorderWidthBottom(0.5f);
		cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
		table.addCell(cell);
		
		PdfPTable wrapper = new PdfPTable(2);
		float[] widths = {2f, 1f};
		wrapper.setWidths(widths);
		wrapper.setWidthPercentage(100);
		
		PdfPCell cell2 = new PdfPCell(table);
		cell2.setHorizontalAlignment(PdfPCell.ALIGN_LEFT);
		cell2.setBorder(0);
		wrapper.addCell(cell2);
		PdfPCell dummyCell = new PdfPCell(new Paragraph(" "));
		dummyCell.setBorder(0);
		wrapper.addCell(dummyCell);
		
		doc.add(wrapper);
	}
	
	private void createDateTime(Report report, PTable table) {
		PChunk cHeading = new PChunk(Messages.getString(
				"PearReview.Reviewer.tabs.information"));
		cHeading.setBold(true);
		cHeading.setColor(new BaseColor(0xC9D200));
		
		PParagraph pHeading = new PParagraph();
		pHeading.setSpacingAfter(10);
		pHeading.add(cHeading);
		
		table.add(pHeading);

		PParagraph pCont = new PParagraph();
		PChunk caption = new PChunk(Messages.getString(
				"PearReview.Moderator.meetings.date") + "\n");
		caption.setBold(true);
		pCont.add(caption);
		
		Date now = new Date();
		SimpleDateFormat formatter = new SimpleDateFormat("dd.MM.yyyy");
		
		PChunk cCont = new PChunk(formatter.format(now));
		pCont.add(cCont);
		table.add(pCont, 20);
		
		pCont = new PParagraph();
		caption = new PChunk(Messages.getString(
				"PearReview.Scribe.attendees.preparation_time") + "\n");
		caption.setBold(true);
		pCont.add(caption);
		
		cCont = new PChunk(millisToString(report.getPreparationTime()));
		pCont.add(cCont);
		table.add(pCont, 20);
	}
	
	/**
	 * 
	 * @param report
	 * @param table
	 * @throws DocumentException
	 */
	private void createResultText(Report report, PTable table) 
			throws DocumentException {
		
		PParagraph pFirst = new PParagraph();
		pFirst.setSpacingAfter(20f);
		table.add(pFirst);
		
		PChunk cHeading = new PChunk(Messages.getString(
				"PearReview.protocol.result") + "\n\n\n");
		cHeading.setFontSize(20);
		
		PChunk caption = new PChunk(Messages.getString(
				"PearReview.protocol.general_impression") + "\n\n");
		caption.setBold(true);
		caption.setColor(new BaseColor(0xC9D200));
		
		PParagraph pName = new PParagraph();
		PChunk content = (isEmpty(report.getImpression()))?
				this.getEmptyFieldChunk():
				new PChunk(report.getImpression());
		pName.add(cHeading);
		pName.add(caption);
		pName.add(content);
		table.add(pName, 20);
		
		
		caption = new PChunk(Messages.getString(
				"PearReview.protocol.final_recommendation") + "\n\n");
		caption.setBold(true);
		caption.setColor(new BaseColor(0xC9D200));
		
		pName = new PParagraph();
		content = (isEmpty(report.getRecommendation()))?
				this.getEmptyFieldChunk():
				new PChunk(report.getRecommendation());
		pName.add(caption);
		pName.add(content);
		table.add(pName, 40);
	}
	
	/**
	 * 
	 * @param review
	 * @param doc
	 * @throws DocumentException
	 */
	private void createProductText(Report report, PTable table) 
			throws DocumentException {
		PChunk cHeading = new PChunk(Messages.getString(
				"PearReview.invitation.product_information"));
		cHeading.setBold(true);
		cHeading.setColor(new BaseColor(0xC9D200));
		
		PParagraph pHeading = new PParagraph();
		pHeading.setSpacingAfter(10);
		pHeading.add(cHeading);
		
		table.add(pHeading);
		
		//add product name
		PChunk caption = new PChunk(Messages.getString(
				"PearReview.Moderator.review.name_2"));
		caption.setBold(true);
		
		Chunk content = (isEmpty(report.getProduct().getName()))?
				getEmptyFieldChunk():
				new Chunk(report.getProduct().getName());
		
		PParagraph p = new PParagraph();
		p.add(caption);
		p.add("\n");
		p.add(content);
		table.add(p);
		
		//add product version
		caption = new PChunk(Messages.getString(
				"PearReview.invitation.version"));
		caption.setBold(true);
		
		content = (isEmpty(report.getProduct().getVersion()))?
				getEmptyFieldChunk():
				new Chunk(report.getProduct().getVersion());
		
		p = new PParagraph();
		p.add(caption);
		p.add("\n");
		p.add(content);
		table.add(p);
		
		
		//add product references
		ArrayList<File> fileRef = new ArrayList<File>();
		ArrayList<String> stringRef = new ArrayList<String>();
		
		if(report.getProduct().getReference() != null) {
			for(String ref: report.getProduct().getReference()) {
				File file = getFile(ref);
				if (file != null) {
					fileRef.add(file);
				} else {
					stringRef.add(ref);
				}
			}
		}
	}
	
	
	/**
	 * Create and add review information.
	 * 
	 * @param review
	 * @param doc
	 * @throws DocumentException
	 */
	private void writeReviewInformation(Report report, PTable table) 
			throws DocumentException {
		PParagraph pFirst = new PParagraph();
		pFirst.setSpacingAfter(40f);
		table.add(pFirst);
		
		PChunk cHeading = new PChunk(Messages.getString(
				"PearReview.invitation.review_information") + "\n\n\n");
		cHeading.setFontSize(20);
		
		
		//add review name
		PChunk caption = new PChunk(Messages.getString(
				"PearReview.Moderator.review.name_2") + "\n\n");
		caption.setBold(true);
		caption.setColor(new BaseColor(0xC9D200));
		
		PChunk content = (isEmpty(report.getName()))?
				getEmptyFieldChunk():
				new PChunk(report.getName());
				
		PParagraph p = new PParagraph();
		p.add(cHeading);
		p.add(caption);
		p.add(content);
		table.add(p, 20);
		
		//add review description
		caption = new PChunk(Messages.getString(
				"PearReview.Moderator.review.description") + "\n\n");
		caption.setBold(true);
		caption.setColor(new BaseColor(0xC9D200));
		
		content = (isEmpty(report.getDescription()))?
				getEmptyFieldChunk():
				new PChunk(report.getDescription());
		
		p = new PParagraph();
		p.add(caption);
		p.add(content);
		table.add(p, 20);
		
		
		//add review comments
		caption = new PChunk(Messages.getString(
				"PearReview.Moderator.review.comments") + "\n\n");
		caption.setBold(true);
		caption.setColor(new BaseColor(0xC9D200));
		
		content = (isEmpty(report.getComments()))?
				getEmptyFieldChunk():
				new PChunk(report.getComments());
		
		p = new PParagraph();
		p.add(caption);
		p.add(content);
		table.add(p, 20);
	}
	
	private String getEmptyField() {
		return Messages.getString("PearReview.protocol.empty");
	}
	
	private PChunk getEmptyFieldChunk() {
		PChunk c = new PChunk(getEmptyField());
		Font f = c.getFont();
		f.setStyle(Font.ITALIC);
		
		return c;
	}
	
	private boolean isEmpty(String str) {
		if (!(str == null || str.equals(""))) {
			return false;
		} else {
			return true;
		}
	}
	
	/**
	 * Checks if given string is an file path
	 * @param str
	 * @return
	 */
	public boolean isFile(String str) {
		return getFile(str) != null;
	}
	
	/**
	 * filters all file strings from given string list
	 * @param lst list to filter
	 * @return filtered list
	 */
	public List<String> getFileList(List<String> lst) {
		if(lst == null)
			return null;
		
		List<String> filteredList = new LinkedList<String>();
		
		for(String str: lst) {
			if(isFile(str))
				filteredList.add(str);
		}
		
		return filteredList;
	}
	
	public File getFile(String fileString) {
		if(!fileString.matches(FILE_PATTERN))
			return null;
		
		File f = null;
		
		try {
			f = new File(new URI(fileString));
		} catch(URISyntaxException e) {
			
		}
		
		return f;
	}
	

	public String millisToString(long millisTime) {
		
		millisTime /= 1000;
		int seconds = (int)(millisTime%60); millisTime /= 60;
		int minutes = (int)(millisTime%60); millisTime /= 60;
		int hours = (int)(millisTime%60);
		
		String secondsStr = Integer.toString(seconds); 
		if (seconds < 10)
			secondsStr = "0" + secondsStr;
		
		String minutesStr = Integer.toString(minutes); 
		if (minutes < 10)
			minutesStr = "0" + minutesStr;
		
		String hoursStr = Integer.toString(hours); 
		if (hours < 10)
			hoursStr = "0" + hoursStr;
			
		return hoursStr + ":" + minutesStr + " " + Messages.getString("" +
				"PearReview.protocol.hours");
	}
	
	private class PCell extends PdfPCell {
		
		public PCell(Paragraph p) {
			super();
			setBorder(0);
			addElement(p);
		}
		
		public PCell(Paragraph p, int i) {
			super();
			setBorder(0);
			setPaddingBottom(i);
			addElement(p);
		}
	}
	
	private class PTable extends PdfPTable {
		
		public PTable(int i) {
			super(i);
		}
		
		public void add(Paragraph p) {
			this.addCell(new PCell(p));
		}
		
		public void add(Paragraph p, int i) {
			this.addCell(new PCell(p, i));
		}
	}
}